/*
 * cryptomfs - a macfuse implementation of cryptofs.
 * Copyright (C) 2008-2009  Greg J. Hedlund
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */


/*
 * 2010-02-22 (GJH)
 *	- @CRYPT_PKG now accepted as a path prefix for volumeicon and mount path
 *	- @CRYPT_PKG/mount is now the default mount location for the filesystem
 *
 * 2009-04-11 (GJH)
 *	- mount_cryptomfs now exepects the .cryptomfs pacakge as the 'root'
 *	- mount_cryptomfs now reads the .plist configuration, any command-line
 *		options will override those found in the .plist file.
 *  - added the ability to provide password on the command line
 *	- added a command line option for opening Finder after mount
 */

#define _GNU_SOURCE

#include "../cryptofs/config.h"
#include "../cryptofs/crypto.h"
#include "../cryptofs/utils.h"
#include <fuse.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <glib.h>
#include "cryptomfs.h"
#include "cryptomfsconfig.h"
#include "cryptomfspw.h"
#include "fs.h"

static gchar *rootpath = NULL;
static gchar *cipher = NULL;
static gchar *md = NULL;
static gchar *volume_name = NULL;
static gchar *custom_icon = NULL;
static gchar *mountpoint = NULL;
static gint blocksize = -1;
static gint salts = -1;
static gchar *cmdPassword = NULL;
static gboolean useDlg = FALSE;

static GOptionEntry entries[] = 
{
  { "blocksize", 0, 0, G_OPTION_ARG_INT, &blocksize, "Set blocksize to BS (default 2048)", "<blocksize:int>" },
  { "cipher", 0, 0, G_OPTION_ARG_STRING, &cipher, "Set cipher  (default 'AES256')", "<cipher:one of(AES256)>" },
  { "md", 0, 0, G_OPTION_ARG_STRING, &md, "Set md (default 'MD5')", "<md:one of (MD5)>" },
  { "salts", 0, 0, G_OPTION_ARG_INT, &salts, "Set salts (default 256)", "<salts:int>" },
  { "volumename", 0, 0, G_OPTION_ARG_STRING, &volume_name, "Set Finder volume name", "<volume name:string>" },
  { "volumeicon", 0, 0, G_OPTION_ARG_STRING, &custom_icon, "Set Finder volume icon", "<volume icon:string>" },
  { "password", 0, 0, G_OPTION_ARG_STRING, &cmdPassword, "Provide password on command line.", "<password:string>" },
  { "dialog", 0, 0, G_OPTION_ARG_NONE, &useDlg, "Use UI dialog for password entry.", NULL },
  { NULL }
};

typedef void (*freeFunc) (void *);

int cryptomfs_main(int argc, char *argv[])
{
    GError *error = NULL;
    GOptionContext *context;
	gchar *realRootpath;
	
	CryptomfsConfig config;
	bzero(&config, sizeof(CryptomfsConfig));

    umask(0);

    context = g_option_context_new ("<.cryptofs package> [<mountpoint>]");
	g_option_context_set_ignore_unknown_options(context, FALSE);
    g_option_context_add_main_entries (context, entries, NULL);
    if(!g_option_context_parse (context, &argc, &argv, &error))
	{
		fprintf(stderr, "Error parsing options: %s\n", error->message);
		return 1;
	}
	
	/*
	 * Make sure we have at least two arguments left.
	 * argv[1] = path to .crpytofs pacakge
	 * argv[2] = path to mountpoint (optional)
	 */
	if(argc < 2 || argc > 3) {
		fprintf(stderr, "%s\n", g_option_context_get_help(context, TRUE, NULL));
		return 1;
	}
	
	rootpath = argv[1];
	if(argc == 3)
		mountpoint = argv[2];

	// make sure a root was given
    if (rootpath == NULL) {
		fprintf(stderr, "Error: No path for encrypted directory specified (see --help)\n");
		return 1;
    }
	
	// copy rootpath into config.packageroot
	strncpy(config.packageroot, rootpath, MAX_PATH);
	
	// attempt to read in the config
	gchar *configPath = g_strjoin("/", rootpath, "cryptofs.plist", NULL);
	if(!readConfig(configPath, &config))
	{
		fprintf(stderr, "Warning: Could not read config file at %s\n", configPath);
	}
	
	unsigned int freeMountPoint = 0;
	if (mountpoint == NULL) {
		// check mount path in config
		if(strlen(config.mountPath) > 0) {
			if(g_str_has_prefix(config.mountPath, CRYPT_PKG)) {
				gchar *suffix = (config.mountPath + strlen(CRYPT_PKG)+1);
				mountpoint = g_strjoin("/", rootpath, suffix, NULL);
				freeMountPoint = 1;
			} else {
				mountpoint = config.mountPath;
			}
			
			// check to make sure mountpoint exists, if not attempt to create it
			if(!g_file_test(mountpoint, G_FILE_TEST_EXISTS)) {
				printf("Creating mount point %s\n", mountpoint);
				
				if(g_mkdir_with_parents(mountpoint, 0700) != 0) {
					fprintf(stderr, "Count not create directory %s\n", mountpoint);
					return 1;
				}
			} else {
				// make sure mountpoint is a directory
				if(!g_file_test(mountpoint, G_FILE_TEST_IS_DIR)) {
					fprintf(stderr, "Error: %s is not a directory\n", mountpoint);
					return 1;
				}
			}
		} else {
			fprintf(stderr, "Error: No mount point specified (see --help)\n");
			return 1;
		}
	}
	
	// setup default options
	if (cipher == NULL) {
		if(strlen(config.cipher) > 0)
			cipher = config.cipher;
		else
			cipher = DEFAULT_CIPHER;
	}
	
	if (md == NULL) {
		if(strlen(config.md) > 0)
			md = config.md;
		else
			md = DEFAULT_MD;
	}
	
	if (blocksize < 0) {
		if(config.blocksize > 0)
			blocksize = config.blocksize;
		else
			blocksize = DEFAULT_BLOCKSIZE;
	}
	
	if (salts < 0) {
		if(config.salts > 0)
			salts = config.salts;
		else
			salts = DEFAULT_SALTS;
	}

	// fix paths
	realRootpath = g_strjoin("/", rootpath, "crypt", NULL);
    if (!g_path_is_absolute(realRootpath)) {
		gchar *oldpath = realRootpath;
		gchar *curpath = g_get_current_dir();

		realRootpath = g_strconcat(curpath, G_DIR_SEPARATOR_S, realRootpath, NULL);
		g_free(curpath);
    }
	
	if (!g_path_is_absolute(mountpoint)) {
		gchar *oldpath = mountpoint;
		gchar *curpath = g_get_current_dir();
		
		mountpoint = g_strconcat(curpath, G_DIR_SEPARATOR_S, mountpoint, NULL);
		g_free(curpath);
	}

	// setup dialog
	if(useDlg)
		cryptomfs_set_authenticaion_mode(_AUTH_MODE_UI_);
	else
		cryptomfs_set_authenticaion_mode(_AUTH_MODE_CONSOLE_);
	
	cryptomfs_set_config(configPath, &config);
	// keychain account name
	cryptomfs_set_keychain_where(realRootpath);
	
	PasswordQuery *query = NULL;
	if(cmdPassword)
		query = cryptomfs_get_copy_password_query(cmdPassword);
	else
		query = cryptomfs_get_default_password_query();
    
	fs_init(realRootpath, crypto_create_global_ctx(cipher, md, blocksize, salts, query));
		
	// setup fuse arguments
	GString *fuseargs = NULL;
	
	if(volume_name == NULL) {
		if(strlen(config.volumeName) > 0)
			volume_name = config.volumeName;  	}
	
	if	(volume_name != NULL) {
		if (fuseargs == NULL)
			fuseargs = g_string_new("-ovolname=");
		else
			g_string_append(fuseargs, ",volname=");
		g_string_append(fuseargs, volume_name);
	}
	
	unsigned int freeIcon = 0;
	if(custom_icon == NULL)
	{
		if(strlen(config.volumeIcon) > 0) {
			// custom_icon = config.volumeIcon;
			
			if(g_str_has_prefix(config.volumeIcon, CRYPT_PKG)) {
				gchar *suffix = (config.volumeIcon + strlen(CRYPT_PKG)+1);
				custom_icon = g_strjoin("/", rootpath, suffix, NULL);
				freeIcon = 1;
			} else {
				custom_icon = config.volumeIcon;
			}
		}
	}
	
	if (custom_icon != NULL) {
		// make sure file exists
		if(g_file_test(custom_icon, G_FILE_TEST_EXISTS)) {
			if (fuseargs == NULL)
				fuseargs = g_string_new("-ovolicon=");
			else
				g_string_append(fuseargs, ",volicon=");
			g_string_append(fuseargs, custom_icon);
		} else {
			fprintf(stderr, "Warning: Icon not found at %s\n", custom_icon);
		}
	}
	
	int numArgs = (fuseargs == NULL ? 2 : 3);
	
	gchar **fuseargv = (gchar**)malloc(numArgs*sizeof(*fuseargv));
	
	fuseargv[0] = argv[0];
	fuseargv[1] = mountpoint;
	
	if(fuseargs != NULL)
		fuseargv[2] = g_string_free(fuseargs, FALSE);
	
	int retVal = 
		fuse_main(numArgs, fuseargv, fs_get_fuse_operations(), NULL);
	
	if (numArgs == 3) 
		g_free(fuseargv[2]);
	g_free(fuseargv);
	g_free(realRootpath);
	if(freeIcon)
		g_free(custom_icon);
	if(freeMountPoint)
		g_free(mountpoint);
	
    return retVal;
}
